package com.cellngine.hub;

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Vector;

import com.cellngine.Constants;
import com.cellngine.JO;
import com.cellngine.cellngine;

public class WebServerHandler implements Runnable
{
	private Constants.hub_http_methods	method			= null;
	private String						request_path	= null;
	
	private String						content_type	= null;
	private int							status			= 500;
	
	private Socket						socket;
	private BufferedReader				sin				= null;
	private OutputStream				sout			= null;
	private PrintStream					out				= null;
	private Vector<byte[]>				fileQueue		= null;
	
	public WebServerHandler(final Socket socket)
	{
		this.socket = socket;
	}
	
	private void setContentType(final String content_type)
	{
		this.content_type = content_type;
	}
	
	private void setStatus(final int status)
	{
		this.status = status;
	}
	
	private void processOutputStream()
	{
		this.out.print("HTTP/1.1 ");
		switch (this.status)
		{
			case 200:
				this.out.print("200 OK");
				break;
			case 400:
				this.out.print("400 Bad Request");
				break;
			case 403:
				this.out.print("403 Forbidden");
				break;
			case 404:
				this.out.print("404 Not Found");
				break;
			case 405:
				this.out.print("405 Method Not Allowed");
				break;
			case 500:
				this.out.print("500 Internal Server Error");
				break;
		}
		this.out.print("\r\nConnection: close\r\n");
		
		if (this.status == 200)
		{
			this.out.print("Content-type: " + this.content_type + "\r\n");
		}
		
		this.out.print("Server: cellngine.hub/" + cellngine.getVersion() + "\r\n");
		this.out.print("\r\n");
		
		if (this.fileQueue != null)
		{
			for (final byte[] buffer : this.fileQueue)
			{
				try
				{
					this.sout.write(buffer);
				}
				catch (final IOException e)
				{
					cellngine.log(e, Constants.log_type.error);
				}
			}
		}
	}
	
	private boolean sendFile(final File file)
	{
		if (this.fileQueue == null)
		{
			this.fileQueue = new Vector<byte[]>();
		}
		
		try
		{
			this.fileQueue.add(JO.getBytesFromInputStream(new FileInputStream(file), true));
			return true;
		}
		catch (final IOException e)
		{
			return false;
		}
	}
	
	private void processLine(final String line)
	{
		if (line.matches("(GET|POST|PUT) [a-zA-Z0-9\\/\\.]* HTTP/1.(0|1)"))
		{
			final String method = line.substring(0, line.indexOf(" ")).toLowerCase();
			if (method.equals("get"))
			{
				this.method = Constants.hub_http_methods.get;
			}
			else if (method.equals("post"))
			{
				this.method = Constants.hub_http_methods.post;
			}
			else if (method.equals("put"))
			{
				this.method = Constants.hub_http_methods.put;
			}
			
			final int pos1 = line.indexOf(" ") + 1;
			final int pos2 = line.lastIndexOf(" ");
			
			String path = line.substring(pos1, pos2);
			if (path.endsWith("/"))
			{
				path += "index.html";
			}
			
			if (path.contains("..") || path.contains("/.") || path.endsWith("~"))
			{
				return;
			}
			
			this.request_path = path;
		}
		else
		{
			cellngine.log("com.cellngine.hub.WebServer: Unknown header: " + line,
					Constants.log_type.debug);
		}
	}
	
	private void processRequest()
	{
		if (this.method == null || this.request_path == null)
		{
			this.setStatus(400);
			return;
		}
		
		if (this.method == Constants.hub_http_methods.get)
		{
			final File file = new File(cellngine.getPath() + "/public_html" + this.request_path);
			
			if (file.exists() && !file.isDirectory() && this.sendFile(file))
			{
				this.setStatus(200);
				
				this.setContentType(MIME.getContentType(this.request_path));
			}
			else
			{
				this.setStatus(404);
			}
		}
		else
		{
			this.setStatus(405);
		}
	}
	
	@Override
	public void run()
	{
		try
		{
			this.sin = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
			this.sout = new BufferedOutputStream(this.socket.getOutputStream());
			this.out = new PrintStream(this.sout);
			
			String line;
			while (!(line = this.sin.readLine()).trim().isEmpty())
			{
				this.processLine(line);
			}
			
			this.processRequest();
			this.processOutputStream();
			
			this.sout.flush();
			this.socket.close();
		}
		catch (final Exception e)
		{
			cellngine.log(e, Constants.log_type.error);
		}
	}
}